#include <sdk.h>
#include "highlighter.h"
#include <M_Code_StyledTextCtrl.h>
#include <M_Code_colourmanager.h>

Highlighter::Highlighter( std::set<wxString> &texts ):
  m_Texts( texts ),
  m_AlreadyChecked( false ),
  m_OldCtrl( NULL ),
  m_HighlightSelectedOccurencesLastPos( -1, -1 ),
  m_OldHighlightSelectionCtrl( NULL ) {
}

Highlighter::~Highlighter() {
  ClearAllIndications();
}

void Highlighter::Call( M_Code_Editor* ctrl, wxScintillaEvent &event ) const {
  if( Manager::Get()->GetEditorManager()->GetActiveEditor() != ctrl ) {
    return;
  }
  if( event.GetEventType() == wxEVT_SCI_UPDATEUI || event.GetEventType() == wxEVT_SCI_PAINTED ) {
    HighlightOccurrencesOfSelection( ctrl );
    OnEditorUpdateUI( ctrl );
  } else if( event.GetEventType() == wxEVT_SCI_MODIFIED ) {
    if( event.GetModificationType() & wxSCI_MOD_INSERTTEXT ) {
      OnEditorChangeTextRange( ctrl, event.GetPosition(), event.GetPosition() + event.GetLength() );
    } else if( event.GetModificationType() & wxSCI_MOD_DELETETEXT ) {
      OnEditorChangeTextRange( ctrl, event.GetPosition(), event.GetPosition() );
    } else if( event.GetModificationType() & wxSCI_MOD_CHANGESTYLE ) {
      OnEditorChangeTextRange( ctrl, event.GetPosition(), event.GetPosition() + event.GetLength() );
    }
  }
}

void Highlighter::OnEditorChange( M_Code_Editor* ctrl ) const {
  m_AlreadyChecked = false;
}

const int Highlighter::GetIndicator()const {
  const int theIndicator = 12;
  return theIndicator;
}

const wxColor Highlighter::GetIndicatorColor()const {
  return Manager::Get()->GetColourManager()->GetColour( _T( "editor_highlight_occurrence_permanently" ) );
}

void Highlighter::OnEditorUpdateUI( M_Code_Editor* ctrl ) const {
  DoSetIndications( ctrl );
}

void Highlighter::OnEditorChangeTextRange( M_Code_Editor* ctrl, int start, int end )const {
  if( m_AlreadyChecked && m_OldCtrl == ctrl ) {
    M_Code_StyledTextCtrl *stc = ctrl->GetLeftSplitViewControl();
    if( end < start ) {
      int t = start;
      start = end;
      end = t;
    }
    start = stc->PositionFromLine( stc->LineFromPosition( start ) );
    end = stc->GetLineEndPosition( stc->LineFromPosition( end ) );
    if( m_InvalidatedRangesStart.GetCount() == 0
        || m_InvalidatedRangesStart.Last() != start
        || m_InvalidatedRangesEnd.Last() != end ) {
      m_InvalidatedRangesStart.Add( start );
      m_InvalidatedRangesEnd.Add( end );
    }
  } else {
    m_AlreadyChecked = false;
  }
}

void Highlighter::TextsChanged()const {
  EditorManager *edmgr = Manager::Get()->GetEditorManager();
  if( !edmgr ) {
    return;
  }
  for( int index = 0 ; index < edmgr->GetEditorsCount() ; ++index ) {
    M_Code_Editor *ed = edmgr->GetBuiltinEditor( index );
    if( ed ) {
      m_AlreadyChecked = false;
      m_OldCtrl = NULL;
      DoSetIndications( ed );
    }
  }
}

static void SetupIndicator( M_Code_StyledTextCtrl *control, int indicator, const wxColor &colour ) {
  control->IndicatorSetForeground( indicator, colour );
  control->IndicatorSetStyle( indicator, wxSCI_INDIC_ROUNDBOX );
  control->IndicatorSetAlpha( indicator, 100 );
  control->IndicatorSetOutlineAlpha( indicator, 255 );
  #ifndef wxHAVE_RAW_BITMAP
  control->IndicatorSetUnder( indicator, true );
  #endif
}

void Highlighter::DoSetIndications( M_Code_Editor* ctrl )const {
  M_Code_StyledTextCtrl *stc = ctrl->GetLeftSplitViewControl();
  M_Code_StyledTextCtrl *stcr = ctrl->GetRightSplitViewControl();
  if( m_AlreadyChecked && m_OldCtrl == ctrl ) {
    if( m_InvalidatedRangesStart.GetCount() == 0 ) {
      return;
    }
  } else {
    m_InvalidatedRangesStart.Clear();
    m_InvalidatedRangesEnd.Clear();
    m_InvalidatedRangesStart.Add( 0 );
    m_InvalidatedRangesEnd.Add( stc->GetLength() );
  }
  m_AlreadyChecked = true;
  stc->SetIndicatorCurrent( GetIndicator() );
  if( m_OldCtrl != ctrl ) {
    SetupIndicator( stc, GetIndicator(), GetIndicatorColor() );
  }
  if( stcr ) {
    if( m_OldCtrl != ctrl ) {
      stcr->SetIndicatorCurrent( GetIndicator() );
      SetupIndicator( stcr, GetIndicator(), GetIndicatorColor() );
    }
  }
  m_OldCtrl = ctrl;
  ConfigManager* cfg = Manager::Get()->GetConfigManager( _T( "editor" ) );
  int flag = 0;
  if( cfg->ReadBool( _T( "/highlight_occurrence/case_sensitive_permanently" ), true ) ) {
    flag |= wxSCI_FIND_MATCHCASE;
  }
  if( cfg->ReadBool( _T( "/highlight_occurrence/whole_word_permanently" ), true ) ) {
    flag |= wxSCI_FIND_WHOLEWORD;
  }
  for( int i = 0; i < ( int )m_InvalidatedRangesStart.GetCount(); i++ ) {
    int start = m_InvalidatedRangesStart[i];
    int end = m_InvalidatedRangesEnd[i];
    if( start < 0 ) {
      start = 0;
    }
    if( end   < 0 ) {
      end   = 0;
    }
    if( start >= stc->GetLength() ) {
      start = stc->GetLength() - 1;
    }
    if( end   >  stc->GetLength() ) {
      end   = stc->GetLength();
    }
    if( start != end ) {
      stc->IndicatorClearRange( start, end - start );
      for( std::set<wxString>::iterator it = m_Texts.begin();
           it != m_Texts.end(); it++ ) {
        wxString text = *it;
        int startpos = start;
        int endpos = end;
        int lengthFound = 0;
        for( int pos = stc->FindText( startpos, endpos, text, flag, &lengthFound );
             pos != wxSCI_INVALID_POSITION ;
             pos = stc->FindText( pos += text.Len(), endpos, text, flag, &lengthFound ) ) {
          stc->IndicatorFillRange( pos, lengthFound );
        }
      }
    }
  }
  m_InvalidatedRangesStart.Clear();
  m_InvalidatedRangesEnd.Clear();
}

void Highlighter::ClearAllIndications( M_Code_StyledTextCtrl* stc )const {
  if( stc ) {
    stc->SetIndicatorCurrent( GetIndicator() );
    stc->IndicatorClearRange( 0, stc->GetLength() );
  }
}

void Highlighter::ClearAllIndications()const {
  EditorManager *edm = Manager::Get()->GetEditorManager();
  for( int i = 0 ; i < edm->GetEditorsCount() ; ++i ) {
    M_Code_Editor *ed = edm->GetBuiltinEditor( i );
    if( ed ) {
      ClearAllIndications( ed->GetControl() );
    }
  }
}

void Highlighter::HighlightOccurrencesOfSelection( M_Code_Editor* ctrl )const {
  M_Code_StyledTextCtrl *control = ctrl->GetControl();
  const int theIndicator = 10;
  std::pair<long, long> curr;
  control->GetSelection( &curr.first, &curr.second );
  control->SetIndicatorCurrent( theIndicator );
  if( m_OldHighlightSelectionCtrl == control && m_HighlightSelectedOccurencesLastPos == curr ) {
    return;
  }
  m_HighlightSelectedOccurencesLastPos = curr;
  m_OldHighlightSelectionCtrl = control;
  int eof = control->GetLength();
  control->IndicatorClearRange( 0, eof );
  if( curr.first == curr.second ) {
    return;
  }
  wxString selectedText( control->GetTextRange( curr.first, curr.second ) );
  if( selectedText.find_first_of( _T( " \t\n" ) ) != wxString::npos ) {
    return;
  }
  ConfigManager* cfg = Manager::Get()->GetConfigManager( _T( "editor" ) );
  if( !cfg->ReadBool( _T( "/highlight_occurrence/enabled" ), true ) ) {
    return;
  }
  wxString::size_type minLength = std::max( cfg->ReadInt( _T( "/highlight_occurrence/min_length" ), 3 ), 1 );
  if( selectedText.length() >= minLength ) {
    wxColour highlightColour( Manager::Get()->GetColourManager()->GetColour( _T( "editor_highlight_occurrence" ) ) );
    if( auto *stc = ctrl->GetLeftSplitViewControl() ) {
      SetupIndicator( stc, theIndicator, highlightColour );
    }
    if( auto stc = ctrl->GetRightSplitViewControl() ) {
      SetupIndicator( stc, theIndicator, highlightColour );
    }
    int flag = 0;
    if( cfg->ReadBool( _T( "/highlight_occurrence/case_sensitive" ), true ) ) {
      flag |= wxSCI_FIND_MATCHCASE;
    }
    if( cfg->ReadBool( _T( "/highlight_occurrence/whole_word" ), true ) ) {
      flag |= wxSCI_FIND_WHOLEWORD;
    }
    typedef std::vector<std::pair<long, long> > Selections;
    Selections selections;
    int count = control->GetSelections();
    for( int ii = 0; ii < count; ++ii ) {
      selections.push_back( Selections::value_type( control->GetSelectionNStart( ii ),
                            control->GetSelectionNEnd( ii ) ) );
    }
    std::sort( selections.begin(), selections.end() );
    Selections::const_iterator currSelection = selections.begin();
    int lengthFound = 0;
    for( int pos = control->FindText( 0, eof, selectedText, flag, &lengthFound );
         pos != wxSCI_INVALID_POSITION ;
         pos = control->FindText( pos += selectedText.Len(), eof, selectedText, flag, &lengthFound ) ) {
      bool skip = false;
      for( ; currSelection != selections.end(); ++currSelection ) {
        if( currSelection->second < pos ) {
          continue;
        }
        if( pos + lengthFound >= currSelection->first ) {
          skip = true;
        }
        break;
      }
      if( skip ) {
        continue;
      }
      control->IndicatorFillRange( pos, lengthFound );
    }
  }
}
